package com.gmail.bloim.serg.testrunner;

import com.gmail.bloim.serg.io.ForkOutputStream;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * User: Администратор
 * Date: 25.04.13
 * Time: 11:53
 */
public class TestExecuter {

    private final Method mainMethod;
    private ArrayList<TestExecutionSubscriber> subscribers = new ArrayList<TestExecutionSubscriber>();

    public TestExecuter(String mainClass) {
        try {
            mainMethod = Class.forName(mainClass).getMethod("main", String[].class);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Smth wrong while trying to get candidate's main method", e);
        }
    }

    public void subscribeToTestRunOutput(TestExecutionSubscriber subscriber) {
        subscribers.add(subscriber);
    }

    public boolean runTest(Test test) throws TestEnvironmentException, TestException {
        ByteArrayOutputStream testOut = new ByteArrayOutputStream();

        PrintStream sysOut = System.out;
        PrintStream sysErr = System.err;
        try {
            ArrayList<OutputStream> outStreams = new ArrayList<OutputStream>(subscribers.size());
            for (TestExecutionSubscriber subscriber : subscribers) {
                try {
                    outStreams.add(subscriber.createOutputStream(test));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            ArrayList<OutputStream> errStreams = new ArrayList<OutputStream>(subscribers.size());
            for (TestExecutionSubscriber subscriber : subscribers) {
                try {
                    errStreams.add(subscriber.createErrorStream(test));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            OutputStream outStream = new ForkOutputStream.Builder().add(testOut).add(outStreams).build();
            OutputStream errStream = new ForkOutputStream.Builder().add(testOut).add(errStreams).build();
            System.setOut(new PrintStream(outStream));
            System.setErr(new PrintStream(errStream));
            InputStream testStdIn = test.createStdIn();
            System.setIn(testStdIn);
            String[] args = test.getArgs();
            try {
                mainMethod.invoke(null, (Object) args);
            } catch (InvocationTargetException e) {
                throw new TestException(e.getCause());
            }
            try {
                outStream.close();
            } catch (IOException e) {
                e.printStackTrace(sysErr);
            }
            try {
                errStream.close();
            } catch (IOException e) {
                e.printStackTrace(sysErr);
            }
            try {
                testStdIn.close();
            } catch (IOException e) {
                e.printStackTrace(sysErr);
            }
            System.setOut(sysOut);
            System.setErr(sysErr);
            SimpleTestResultComparator cmp = new SimpleTestResultComparator();
            return cmp.judge(test, testOut.toByteArray());
        } catch (TestException te) {
            te.printStackTrace();
            throw te;
        } catch (Throwable t) {
            throw new TestEnvironmentException(t);
        } finally {
            System.setOut(sysOut);
            System.setErr(sysErr);
        }
    }
}
